"""
版本管理服务层
将版本相关的业务逻辑从API层迁移到服务层
"""
from datetime import datetime
from typing import List, Optional, Dict, Any
from collections import Counter
from bson import ObjectId  # pyright: ignore[reportMissingImports]
from motor.motor_asyncio import AsyncIOMotorDatabase  # pyright: ignore[reportMissingImports]
from fastapi import HTTPException  # pyright: ignore[reportMissingImports]
from pymongo import ASCENDING, DESCENDING  # pyright: ignore[reportMissingImports]
import logging
import asyncio

try:
    from fastapi import status as http_status_module  # pyright: ignore[reportMissingImports]
except ImportError:
    from starlette import status as http_status_module  # pyright: ignore[reportMissingImports]

from app.models.version import (
    VersionCreate,
    VersionUpdate,
    ReleaseType,
    Environment
)
from app.core.permissions import Permissions
from app.utils.state_utils import get_state_code_by_name_keywords
from app.services.version_transition_rules import VersionTransitionRules

logger = logging.getLogger(__name__)


def serialize_mongo_result(value):
    """
    递归序列化 MongoDB 查询结果
    将 ObjectId 转换为字符串，将 datetime 转换为 ISO 格式字符串
    """
    if value is None:
        return None
    if isinstance(value, ObjectId):
        return str(value)
    if isinstance(value, datetime):
        return value.isoformat()
    if isinstance(value, dict):
        return {k: serialize_mongo_result(v) for k, v in value.items()}
    if isinstance(value, list):
        return [serialize_mongo_result(item) for item in value]
    return value


class VersionService:
    """版本服务类，封装版本相关的业务逻辑"""
    
    def __init__(self, db: AsyncIOMotorDatabase):
        self.db = db
        self._completion_service = None
        self._auto_transition_service = None
        self._approval_service = None
    
    @property
    def completion_service(self):
        """懒加载完成度服务"""
        if self._completion_service is None:
            from app.services.version_completion_service import VersionCompletionService
            self._completion_service = VersionCompletionService(self.db)
        return self._completion_service
    
    @property
    def auto_transition_service(self):
        """懒加载自动流转服务"""
        if self._auto_transition_service is None:
            from app.services.version_auto_transition_service import VersionAutoTransitionService
            self._auto_transition_service = VersionAutoTransitionService(self.db)
        return self._auto_transition_service
    
    @property
    def approval_service(self):
        """懒加载审批服务"""
        if self._approval_service is None:
            from app.services.version_approval_service import VersionApprovalService
            self._approval_service = VersionApprovalService(self.db)
        return self._approval_service
    
    @staticmethod
    def _get_status_code(status_name: str, default: int = 500) -> int:
        """安全地获取HTTP状态码"""
        try:
            if http_status_module is None:
                return default
            return getattr(http_status_module, status_name, default)
        except (AttributeError, TypeError):
            return default
    
    def _to_object_id(self, value: Any, field_name: str = "ID") -> Optional[ObjectId]:
        """将值转换为ObjectId，如果无效则返回None"""
        if value is None:
            return None
        if isinstance(value, ObjectId):
            return value
        if isinstance(value, str) and ObjectId.is_valid(value):
            return ObjectId(value)
        return None
    
    def _validate_object_id(self, value: Any, field_name: str = "ID") -> ObjectId:
        """验证并转换ObjectId，如果无效则抛出异常"""
        obj_id = self._to_object_id(value, field_name)
        if obj_id is None:
            raise HTTPException(
                status_code=self._get_status_code('HTTP_400_BAD_REQUEST', 400),
                detail=f"无效的{field_name}格式"
            )
        return obj_id
    
    async def _check_project_access(
        self,
        project_id: ObjectId,
        current_user: dict,
        include_public: bool = True
    ) -> dict:
        """检查用户是否有项目访问权限，返回项目信息"""
        user_id = ObjectId(current_user.get("user_id"))
        user_permissions = current_user.get("permissions", [])
        
        # 管理员可以访问所有项目
        if "*" in user_permissions or Permissions.VERSION_READ_ALL in user_permissions:
            project = await self.db.projects.find_one({"_id": project_id})
            if project:
                return project
            raise HTTPException(
                status_code=self._get_status_code('HTTP_404_NOT_FOUND', 404),
                detail="Project not found"
            )
        
        # 构建查询条件
        query_conditions = [
            {"created_by": user_id},
            {"members.user_id": user_id}
        ]
        if include_public:
            query_conditions.append({"is_public": True})
        
        project = await self.db.projects.find_one({
            "_id": project_id,
            "$or": query_conditions
        })
        
        if not project:
            raise HTTPException(
                status_code=self._get_status_code('HTTP_403_FORBIDDEN', 403),
                detail="Permission denied"
            )
        
        return project
    
    async def _check_permission(
        self,
        current_user: dict,
        required_permission: str,
        project_id: Optional[ObjectId] = None,
        version_id: Optional[ObjectId] = None
    ) -> bool:
        """检查用户权限"""
        user_permissions = current_user.get("permissions", [])
        
        # 管理员拥有所有权限
        if "*" in user_permissions or required_permission in user_permissions:
            return True
        
        # 如果需要检查项目权限
        if project_id:
            try:
                await self._check_project_access(project_id, current_user, include_public=True)
                return True
            except HTTPException:
                return False
        
        # 如果需要检查版本权限
        if version_id:
            version = await self.db.versions.find_one({"_id": version_id})
            if version:
                try:
                    await self._check_project_access(version["project_id"], current_user, include_public=True)
                    return True
                except HTTPException:
                    return False
        
        return False
    
    async def _get_accessible_project_ids(self, current_user: dict) -> List[ObjectId]:
        """获取用户有权限访问的项目ID列表"""
        user_permissions = current_user.get("permissions", [])
        
        if "*" in user_permissions or Permissions.VERSION_READ_ALL in user_permissions:
            return []  # 空列表表示所有项目
        
        user_id = ObjectId(current_user.get("user_id"))
        project_query = {
            "$or": [
                {"is_public": True},
                {"created_by": user_id},
                {"members.user_id": user_id}
            ]
        }
        
        project_cursor = self.db.projects.find(project_query, {"_id": 1})
        accessible_project_ids = [project["_id"] async for project in project_cursor]
        
        return accessible_project_ids
    
    def _build_version_query_filters(
        self,
        search: Optional[str] = None,
        project_id: Optional[str] = None,
        status: Optional[str] = None,
        release_type: Optional[ReleaseType] = None,
        environment: Optional[Environment] = None,
        created_by: Optional[str] = None
    ) -> dict:
        """构建版本查询条件"""
        query = {}
        
        if search:
            query["$or"] = [
                {"name": {"$regex": search, "$options": "i"}},
                {"description": {"$regex": search, "$options": "i"}},
                {"version_number": {"$regex": search, "$options": "i"}}
            ]
        
        if project_id:
            query["project_id"] = self._validate_object_id(project_id, "project_id")
        
        if status:
            query["status"] = status
        
        if release_type:
            query["release_type"] = release_type
        
        if environment:
            query["environment"] = environment
        
        if created_by:
            query["created_by"] = self._validate_object_id(created_by, "created_by")
        
        return query
    
    def _merge_query_with_permissions(
        self,
        query: dict,
        filter_query: dict,
        accessible_project_ids: Optional[List[ObjectId]]
    ) -> dict:
        """合并权限控制的查询条件和过滤器查询条件"""
        # 如果用户没有任何项目权限，返回空查询（会导致返回空结果）
        if accessible_project_ids is not None and len(accessible_project_ids) > 0:
            # 用户有权限的项目ID列表
            query["project_id"] = {"$in": accessible_project_ids}
            # 如果过滤器中也指定了 project_id，需要合并
            if "project_id" in filter_query:
                filter_project_id = filter_query["project_id"]
                if filter_project_id in accessible_project_ids:
                    query["project_id"] = filter_project_id
                else:
                    # 用户没有权限访问指定的项目
                    return {"_id": {"$in": []}}
        # 空列表表示管理员，可以访问所有项目，直接使用 filter_query
        elif accessible_project_ids is not None and len(accessible_project_ids) == 0:
            # 管理员权限，直接使用 filter_query
            query.update(filter_query)
            return query
        else:
            # 用户没有任何项目权限，返回空查询
            return {"_id": {"$in": []}}
        
        # 合并其他查询条件
        for key, value in filter_query.items():
            if key != "project_id":
                query[key] = value
        
        return query
    
    def _build_version_list_pipeline(
        self,
        query: dict,
        skip: int,
        limit: int,
        sort_by: str,
        sort_order: str
    ) -> List[dict]:
        """构建版本列表的聚合管道"""
        sort_direction = ASCENDING if sort_order == "asc" else DESCENDING
        
        # 构建排序字典：先按置顶状态排序（置顶的在前），再按指定字段排序
        sort_dict = {
            "is_pinned": DESCENDING,  # 置顶的在前
            sort_by: sort_direction
        }
        
        return [
            {"$match": query},
            {"$sort": sort_dict},
            {"$skip": skip},
            {"$limit": limit},
            # 关联项目信息
            {"$lookup": {
                "from": "projects",
                "localField": "project_id",
                "foreignField": "_id",
                "as": "project"
            }},
            {"$unwind": {"path": "$project", "preserveNullAndEmptyArrays": True}},
            # 关联任务统计
            {"$lookup": {
                "from": "version_task_relations",
                "localField": "_id",
                "foreignField": "version_id",
                "as": "task_relations"
            }},
            # 关联缺陷统计
            {"$lookup": {
                "from": "version_defect_relations",
                "localField": "_id",
                "foreignField": "version_id",
                "as": "defect_relations"
            }},
            # 关联发布记录统计
            {"$lookup": {
                "from": "release_records",
                "localField": "_id",
                "foreignField": "version_id",
                "as": "release_records"
            }},
            # 投影字段
            {"$project": {
                "id": {"$toString": "$_id"},
                "name": 1,
                "version_number": 1,
                "description": 1,
                "project_id": {"$toString": "$project_id"},
                "project_name": {"$ifNull": ["$project.name", "Unknown Project"]},
                "start_date": {
                    "$cond": {
                        "if": {"$eq": ["$start_date", None]},
                        "then": None,
                        "else": "$start_date"
                    }
                },
                "end_date": {
                    "$cond": {
                        "if": {"$eq": ["$end_date", None]},
                        "then": None,
                        "else": "$end_date"
                    }
                },
                "status": 1,
                "release_type": {"$ifNull": ["$release_type", None]},
                "environment": {"$ifNull": ["$environment", None]},
                "planned_release_date": {
                    "$cond": {
                        "if": {"$eq": ["$planned_release_date", None]},
                        "then": None,
                        "else": "$planned_release_date"
                    }
                },
                "actual_release_date": {
                    "$cond": {
                        "if": {"$eq": ["$actual_release_date", None]},
                        "then": None,
                        "else": "$actual_release_date"
                    }
                },
                "release_notes": {"$ifNull": ["$release_notes", None]},
                "goals": {"$ifNull": ["$goals", []]},
                "created_by": {"$toString": "$created_by"},
                "created_at": {
                    "$cond": {
                        "if": {"$eq": ["$created_at", None]},
                        "then": None,
                        "else": "$created_at"
                    }
                },
                "updated_at": {
                    "$cond": {
                        "if": {"$eq": ["$updated_at", None]},
                        "then": None,
                        "else": "$updated_at"
                    }
                },
                "planned_work_hours": {
                    "$cond": {
                        "if": {"$eq": ["$planned_work_hours", None]},
                        "then": 0.0,
                        "else": "$planned_work_hours"
                    }
                },
                "task_count": {"$size": {"$ifNull": ["$task_relations", []]}},
                "defect_count": {"$size": {"$ifNull": ["$defect_relations", []]}},
                "release_count": {"$size": {"$ifNull": ["$release_records", []]}}
            }}
        ]
    
    async def _calculate_planned_work_hours(self, version_id: ObjectId) -> float:
        """计算版本关联任务的预估工时总和"""
        pipeline = [
            {"$match": {"version_id": version_id}},
            {"$lookup": {
                "from": "tasks",
                "localField": "task_id",
                "foreignField": "_id",
                "as": "task_docs"
            }},
            {"$unwind": {
                "path": "$task_docs",
                "preserveNullAndEmptyArrays": False
            }},
            {"$group": {
                "_id": None,
                "total": {
                    "$sum": {
                        "$ifNull": ["$task_docs.estimated_hours", 0]
                    }
                }
            }}
        ]
        
        try:
            result = await self.db.version_task_relations.aggregate(pipeline).to_list(length=1)
            if result and result[0]:
                total = result[0].get("total", 0)
                return float(total or 0.0)
        except Exception as exc:  # pylint: disable=broad-except
            logger.warning("计算版本(%s)规划工时失败: %s", str(version_id), str(exc))
        return 0.0
    
    async def _build_version_query(
        self,
        current_user: dict,
        search: Optional[str] = None,
        project_id: Optional[str] = None,
        status: Optional[str] = None,
        release_type: Optional[ReleaseType] = None,
        environment: Optional[Environment] = None,
        created_by: Optional[str] = None
    ) -> dict:
        """构建版本查询条件（包含权限控制）"""
        # 权限控制
        accessible_project_ids = await self._get_accessible_project_ids(current_user)
        
        # 构建过滤器查询条件
        filter_query = self._build_version_query_filters(
            search=search,
            project_id=project_id,
            status=status,
            release_type=release_type,
            environment=environment,
            created_by=created_by
        )
        
        # 合并权限控制的查询条件
        query = self._merge_query_with_permissions({}, filter_query, accessible_project_ids)
        
        return query
    
    async def get_versions(
        self,
        current_user: dict,
        skip: int = 0,
        limit: int = 20,
        search: Optional[str] = None,
        project_id: Optional[str] = None,
        status: Optional[str] = None,
        release_type: Optional[ReleaseType] = None,
        environment: Optional[Environment] = None,
        created_by: Optional[str] = None,
        sort_by: str = "created_at",
        sort_order: str = "desc"
    ) -> Dict[str, Any]:
        """获取版本列表"""
        # 构建查询条件（包含权限控制）
        query = await self._build_version_query(
            current_user=current_user,
            search=search,
            project_id=project_id,
            status=status,
            release_type=release_type,
            environment=environment,
            created_by=created_by
        )
        
        # 如果查询条件为空（用户无权限），直接返回空结果
        if query.get("_id", {}).get("$in") == []:
            return {
                "items": [],
                "total": 0,
                "page": 1,
                "size": limit
            }
        
        # 构建聚合管道
        pipeline = self._build_version_list_pipeline(
            query=query,
            skip=skip,
            limit=limit,
            sort_by=sort_by,
            sort_order=sort_order
        )
        
        # 执行聚合查询和计数查询（并行执行以提高性能）
        try:
            versions, total_count = await asyncio.gather(
                self.db.versions.aggregate(pipeline).to_list(length=limit),
                self.db.versions.count_documents(query)
            )
        except Exception as e:
            logger.error(f"Error querying versions: {str(e)}", exc_info=True)
            raise HTTPException(
                status_code=self._get_status_code('HTTP_500_INTERNAL_SERVER_ERROR', 500),
                detail="Failed to query versions"
            )
        
        # 处理结果
        processed_versions = [serialize_mongo_result(version) for version in versions]
        
        return {
            "items": processed_versions,
            "total": total_count,
            "page": (skip // limit) + 1 if limit > 0 else 1,
            "size": limit
        }
    
    async def get_versions_overview(
        self,
        current_user: dict,
        project_id: Optional[str] = None,
        status: Optional[str] = None,
        release_type: Optional[ReleaseType] = None,
        environment: Optional[Environment] = None,
        created_by: Optional[str] = None,
        limit: int = 50,
        sort_by: str = "planned_release_date",
        sort_order: str = "desc"
    ) -> Dict[str, Any]:
        """获取版本总览数据"""
        query = await self._build_version_query(
            current_user=current_user,
            search=None,
            project_id=project_id,
            status=status,
            release_type=release_type,
            environment=environment,
            created_by=created_by
        )
        
        if query.get("_id", {}).get("$in") == []:
            return {"items": [], "total": 0}
        
        pipeline = self._build_version_list_pipeline(
            query=query,
            skip=0,
            limit=limit,
            sort_by=sort_by,
            sort_order=sort_order
        )
        
        try:
            versions, total_count = await asyncio.gather(
                self.db.versions.aggregate(pipeline).to_list(length=limit),
                self.db.versions.count_documents(query)
            )
        except Exception as exc:  # pylint: disable=broad-except
            logger.error("获取版本总览失败: %s", str(exc), exc_info=True)
            raise HTTPException(
                status_code=self._get_status_code('HTTP_500_INTERNAL_SERVER_ERROR', 500),
                detail="Failed to query version overview"
            )
        
        serialized_versions = [serialize_mongo_result(version) for version in versions]
        version_ids: List[ObjectId] = []
        for item in serialized_versions:
            try:
                version_ids.append(self._validate_object_id(item["id"], "version_id"))
            except HTTPException:
                version_ids.append(None)  # 占位，稍后过滤
        
        completion_results: List[Optional[Dict[str, Any]]] = [None] * len(version_ids)
        completion_coroutines: List = []
        completion_indices: List[int] = []
        for idx, version_id in enumerate(version_ids):
            if version_id:
                completion_coroutines.append(self.get_completion_rate(version_id, use_cache=True))
                completion_indices.append(idx)
        
        if completion_coroutines:
            gathered = await asyncio.gather(*completion_coroutines, return_exceptions=True)
            for pos, result in enumerate(gathered):
                target_index = completion_indices[pos]
                if isinstance(result, Exception):
                    logger.warning("计算版本(%s)完成度失败: %s", serialized_versions[target_index].get("id"), str(result))
                    completion_results[target_index] = None
                else:
                    completion_results[target_index] = result
        
        overview_items: List[Dict[str, Any]] = []
        for index, version in enumerate(serialized_versions):
            completion = completion_results[index] or {}
            details = completion.get("details", {}) or {}
            
            requirement_detail = details.get("requirements", {}) or {}
            task_detail = details.get("tasks", {}) or {}
            defect_detail = details.get("defects", {}) or {}
            
            requirement_stats = {
                "total": requirement_detail.get("total", 0) or 0,
                "completed": requirement_detail.get("completed", 0) or 0
            }
            task_stats = {
                "total": task_detail.get("total", 0) or 0,
                "completed": task_detail.get("completed", 0) or 0
            }
            defect_completed = defect_detail.get("resolved")
            if defect_completed is None:
                defect_completed = defect_detail.get("completed", 0)
            defect_stats = {
                "total": defect_detail.get("total", 0) or 0,
                "completed": defect_completed or 0
            }
            
            progress_value = completion.get("overall_completion", 0.0) or 0.0
            progress_percent = max(0.0, min(100.0, round(progress_value * 100, 2)))
            
            planned_hours = float(version.get("planned_work_hours") or 0.0)
            version_obj_id = version_ids[index]
            if planned_hours <= 0 and version_obj_id:
                planned_hours = await self._calculate_planned_work_hours(version_obj_id)
            
            item = {
                **version,
                "planned_work_hours": round(planned_hours, 2),
                "progress": progress_percent,
                "requirement_stats": requirement_stats,
                "task_stats": task_stats,
                "defect_stats": defect_stats
            }
            overview_items.append(item)
        
        return {
            "items": overview_items,
            "total": total_count
        }
    
    async def get_versions_count(
        self,
        current_user: dict,
        search: Optional[str] = None,
        project_id: Optional[str] = None,
        status: Optional[str] = None,
        release_type: Optional[ReleaseType] = None,
        environment: Optional[Environment] = None,
        created_by: Optional[str] = None
    ) -> int:
        """获取版本总数"""
        # 构建查询条件（包含权限控制）
        query = await self._build_version_query(
            current_user=current_user,
            search=search,
            project_id=project_id,
            status=status,
            release_type=release_type,
            environment=environment,
            created_by=created_by
        )
        
        # 如果查询条件为空（用户无权限），直接返回0
        if query.get("_id", {}).get("$in") == []:
            return 0
        
        try:
            count = await self.db.versions.count_documents(query)
            return count
        except Exception as e:
            logger.error(f"Error counting versions: {str(e)}", exc_info=True)
            return 0
    
    async def get_version(self, version_id: str, current_user: dict) -> Dict[str, Any]:
        """获取版本详情"""
        version_obj_id = self._validate_object_id(version_id, "version_id")
        
        # 查询版本
        version = await self.db.versions.find_one({"_id": version_obj_id})
        if not version:
            raise HTTPException(
                status_code=self._get_status_code('HTTP_404_NOT_FOUND', 404),
                detail="Version not found"
            )
        
        # 权限检查
        user_permissions = current_user.get("permissions", [])
        if "*" not in user_permissions and Permissions.VERSION_READ_ALL not in user_permissions:
            await self._check_project_access(version["project_id"], current_user, include_public=True)
        
        # 使用聚合查询一次性获取所有关联信息
        pipeline = [
            {"$match": {"_id": version_obj_id}},
            # 关联项目信息
            {"$lookup": {
                "from": "projects",
                "localField": "project_id",
                "foreignField": "_id",
                "as": "project"
            }},
            {"$unwind": {"path": "$project", "preserveNullAndEmptyArrays": True}},
            # 关联任务关系
            {"$lookup": {
                "from": "version_task_relations",
                "localField": "_id",
                "foreignField": "version_id",
                "as": "task_relations"
            }},
            # 关联任务详情
            {"$lookup": {
                "from": "tasks",
                "localField": "task_relations.task_id",
                "foreignField": "_id",
                "as": "tasks"
            }},
            # 关联缺陷关系
            {"$lookup": {
                "from": "version_defect_relations",
                "localField": "_id",
                "foreignField": "version_id",
                "as": "defect_relations"
            }},
            # 关联缺陷详情
            {"$lookup": {
                "from": "defects",
                "localField": "defect_relations.defect_id",
                "foreignField": "_id",
                "as": "defects"
            }},
            # 关联需求关系
            {"$lookup": {
                "from": "version_requirement_relations",
                "localField": "_id",
                "foreignField": "version_id",
                "as": "requirement_relations"
            }},
            # 关联需求详情
            {"$lookup": {
                "from": "requirements",
                "localField": "requirement_relations.requirement_id",
                "foreignField": "_id",
                "as": "requirements"
            }},
            # 关联发布记录
            {"$lookup": {
                "from": "release_records",
                "localField": "_id",
                "foreignField": "version_id",
                "as": "release_records"
            }},
            {"$project": {
                "id": {"$toString": "$_id"},
                "name": 1,
                "version_number": 1,
                "description": 1,
                "project_id": {"$toString": "$project_id"},
                "project_name": {"$ifNull": ["$project.name", "Unknown Project"]},
                "start_date": {
                    "$cond": {
                        "if": {"$eq": ["$start_date", None]},
                        "then": None,
                        "else": "$start_date"
                    }
                },
                "end_date": {
                    "$cond": {
                        "if": {"$eq": ["$end_date", None]},
                        "then": None,
                        "else": "$end_date"
                    }
                },
                "status": 1,
                "release_type": 1,
                "environment": 1,
                "planned_release_date": 1,
                "actual_release_date": 1,
                "planned_work_hours": {
                    "$cond": {
                        "if": {"$eq": ["$planned_work_hours", None]},
                        "then": 0.0,
                        "else": "$planned_work_hours"
                    }
                },
                "release_notes": 1,
                "release_config": 1,
                "metadata": 1,
                "goals": {"$ifNull": ["$goals", []]},
                "created_by": {"$toString": "$created_by"},
                "created_at": 1,
                "updated_at": 1,
                "task_count": {"$size": "$task_relations"},
                "defect_count": {"$size": "$defect_relations"},
                "requirement_count": {"$size": "$requirement_relations"},
                "release_count": {"$size": "$release_records"},
                "tasks": {
                    "$map": {
                        "input": "$tasks",
                        "as": "task",
                        "in": {
                            "$let": {
                                "vars": {
                                    "relation": {
                                        "$arrayElemAt": [
                                            {
                                                "$filter": {
                                                    "input": "$task_relations",
                                                    "as": "rel",
                                                    "cond": {"$eq": ["$$rel.task_id", "$$task._id"]}
                                                }
                                            },
                                            0
                                        ]
                                    }
                                },
                                "in": {
                                    "id": {"$toString": "$$task._id"},
                                    "title": "$$task.title",
                                    "status": "$$task.status",
                                    "priority": "$$task.priority",
                                    "assignee_id": {
                                        "$cond": {
                                            "if": {"$ifNull": ["$$task.assignee_id", False]},
                                            "then": {"$toString": "$$task.assignee_id"},
                                            "else": None
                                        }
                                    },
                                    "assignee_name": {"$ifNull": ["$$task.assignee_name", None]},
                                    "requirement_id": {
                                        "$cond": {
                                            "if": {"$ifNull": ["$$relation.requirement_id", False]},
                                            "then": {"$toString": "$$relation.requirement_id"},
                                            "else": None
                                        }
                                    },
                                    "estimated_hours": {"$ifNull": ["$$task.estimated_hours", 0]},
                                    "estimated_effort": {"$ifNull": ["$$task.estimated_effort", 0]},
                                    "actual_hours": {"$ifNull": ["$$task.actual_hours", 0]},
                                    "start_date": {
                                        "$cond": {
                                            "if": {"$eq": ["$$task.start_date", None]},
                                            "then": None,
                                            "else": "$$task.start_date"
                                        }
                                    },
                                    "due_date": {
                                        "$cond": {
                                            "if": {"$eq": ["$$task.due_date", None]},
                                            "then": None,
                                            "else": "$$task.due_date"
                                        }
                                    },
                                    "relation_type": "$$relation.relation_type",
                                    "relation_notes": "$$relation.notes",
                                    "associated_at": "$$relation.created_at"
                                }
                            }
                        }
                    }
                },
                "defects": {
                    "$map": {
                        "input": "$defects",
                        "as": "defect",
                        "in": {
                            "$let": {
                                "vars": {
                                    "relation": {
                                        "$arrayElemAt": [
                                            {
                                                "$filter": {
                                                    "input": "$defect_relations",
                                                    "as": "rel",
                                                    "cond": {"$eq": ["$$rel.defect_id", "$$defect._id"]}
                                                }
                                            },
                                            0
                                        ]
                                    }
                                },
                                "in": {
                                    "id": {"$toString": "$$defect._id"},
                                    "title": "$$defect.title",
                                    "status": "$$defect.status",
                                    "priority": "$$defect.priority",
                                    "severity": "$$defect.severity",
                                    "assignee_id": {
                                        "$cond": {
                                            "if": {"$ifNull": ["$$defect.assignee_id", False]},
                                            "then": {"$toString": "$$defect.assignee_id"},
                                            "else": None
                                        }
                                    },
                                    "assignee_name": {"$ifNull": ["$$defect.assignee_name", None]},
                                    "requirement_id": {
                                        "$cond": {
                                            "if": {"$ifNull": ["$$relation.requirement_id", False]},
                                            "then": {"$toString": "$$relation.requirement_id"},
                                            "else": None
                                        }
                                    },
                                    "task_id": {
                                        "$cond": {
                                            "if": {"$ifNull": ["$$relation.task_id", False]},
                                            "then": {"$toString": "$$relation.task_id"},
                                            "else": None
                                        }
                                    },
                                    "parent_defect_id": {
                                        "$cond": {
                                            "if": {"$ifNull": ["$$relation.parent_defect_id", False]},
                                            "then": {"$toString": "$$relation.parent_defect_id"},
                                            "else": None
                                        }
                                    },
                                    "found_date": {
                                        "$cond": {
                                            "if": {"$eq": ["$$defect.found_date", None]},
                                            "then": None,
                                            "else": "$$defect.found_date"
                                        }
                                    },
                                    "resolved_date": {
                                        "$cond": {
                                            "if": {"$eq": ["$$defect.resolved_date", None]},
                                            "then": None,
                                            "else": "$$defect.resolved_date"
                                        }
                                    },
                                    "relation_type": "$$relation.relation_type",
                                    "relation_notes": "$$relation.notes",
                                    "associated_at": "$$relation.created_at"
                                }
                            }
                        }
                    }
                },
                "requirements": {
                    "$map": {
                        "input": "$requirements",
                        "as": "requirement",
                        "in": {
                            "$let": {
                                "vars": {
                                    "relation": {
                                        "$arrayElemAt": [
                                            {
                                                "$filter": {
                                                    "input": "$requirement_relations",
                                                    "as": "rel",
                                                    "cond": {"$eq": ["$$rel.requirement_id", "$$requirement._id"]}
                                                }
                                            },
                                            0
                                        ]
                                    }
                                },
                                "in": {
                                    "id": {"$toString": "$$requirement._id"},
                                    "title": "$$requirement.title",
                                    "status": "$$requirement.status",
                                    "priority": "$$requirement.priority",
                                    "assignee_id": {
                                        "$cond": {
                                            "if": {"$ifNull": ["$$requirement.assignee_id", False]},
                                            "then": {"$toString": "$$requirement.assignee_id"},
                                            "else": None
                                        }
                                    },
                                    "assignee_name": {"$ifNull": ["$$requirement.assignee_name", None]},
                                    "estimated_effort": {"$ifNull": ["$$requirement.estimated_effort", 0]},
                                    "start_date": {
                                        "$cond": {
                                            "if": {"$eq": ["$$requirement.start_date", None]},
                                            "then": None,
                                            "else": "$$requirement.start_date"
                                        }
                                    },
                                    "due_date": {
                                        "$cond": {
                                            "if": {"$eq": ["$$requirement.due_date", None]},
                                            "then": None,
                                            "else": "$$requirement.due_date"
                                        }
                                    },
                                    "parent_id": {
                                        "$cond": {
                                            "if": {"$ifNull": ["$$requirement.parent_id", False]},
                                            "then": {"$toString": "$$requirement.parent_id"},
                                            "else": None
                                        }
                                    },
                                    "relation_type": "$$relation.relation_type",
                                    "relation_notes": "$$relation.notes",
                                    "associated_at": "$$relation.created_at"
                                }
                            }
                        }
                    }
                },
                "release_records": {
                    "$map": {
                        "input": {"$slice": [{"$reverseArray": "$release_records"}, 10]},
                        "as": "record",
                        "in": {
                            "id": {"$toString": "$$record._id"},
                            "release_type": "$$record.release_type",
                            "environment": "$$record.environment",
                            "status": "$$record.status",
                            "release_date": "$$record.release_date",
                            "rollback_date": "$$record.rollback_date",
                            "notes": "$$record.notes",
                            "created_by": {"$toString": "$$record.created_by"},
                            "created_at": "$$record.created_at"
                        }
                    }
                }
            }}
        ]
        
        result = await self.db.versions.aggregate(pipeline).to_list(length=1)
        
        if not result:
            raise HTTPException(
                status_code=self._get_status_code('HTTP_404_NOT_FOUND', 404),
                detail="Version not found"
            )
        
        version_detail = result[0]
        serialized_detail = serialize_mongo_result(version_detail)
        
        return serialized_detail
    
    async def get_version_work_items(
        self,
        version_id: str,
        current_user: dict
    ) -> Dict[str, Any]:
        """获取版本关联的工作项树"""
        version_detail = await self.get_version(version_id, current_user)
        version_obj_id = self._validate_object_id(version_id, "version_id")
        
        completion_data = await self.get_completion_rate(version_obj_id, use_cache=True)
        completion_details = completion_data.get("details", {}) if completion_data else {}
        
        def _build_stats(source: Dict[str, Any], completed_key: str = "completed") -> Dict[str, int]:
            return {
                "total": int(source.get("total", 0) or 0),
                "completed": int(source.get(completed_key, 0) or 0)
            }
        
        requirement_stats = _build_stats(completion_details.get("requirements", {}) or {})
        task_stats = _build_stats(completion_details.get("tasks", {}) or {})
        defect_stats = _build_stats(completion_details.get("defects", {}) or {}, completed_key="resolved")
        
        def _normalize_item(item: Dict[str, Any], item_type: str) -> Dict[str, Any]:
            return {
                "id": item.get("id"),
                "type": item_type,
                "title": item.get("title"),
                "status": item.get("status"),
                "priority": item.get("priority") or item.get("severity"),
                "assignee_id": item.get("assignee_id"),
                "assignee_name": item.get("assignee_name"),
                "estimated_hours": item.get("estimated_hours"),
                "actual_hours": item.get("actual_hours"),
                "estimated_effort": item.get("estimated_effort"),
                "start_date": item.get("start_date"),
                "due_date": item.get("due_date") or item.get("resolved_date"),
                "relation_type": item.get("relation_type"),
                "relation_notes": item.get("relation_notes"),
                "associated_at": item.get("associated_at"),
                "severity": item.get("severity"),
                "meta": item,
                "children": [],
                "leaf": True
            }
        
        requirement_nodes = [
            _normalize_item(requirement, "requirement")
            for requirement in version_detail.get("requirements", [])
        ]
        task_nodes = [
            _normalize_item(task, "task")
            for task in version_detail.get("tasks", [])
        ]
        defect_nodes = [
            _normalize_item(defect, "defect")
            for defect in version_detail.get("defects", [])
        ]
        
        # 初始化节点 children
        for node in requirement_nodes:
            node["children"] = node.get("children") or []
        for node in task_nodes:
            node["children"] = node.get("children") or []
        for node in defect_nodes:
            node["children"] = node.get("children") or []

        requirement_map = {node["id"]: node for node in requirement_nodes}
        task_map = {node["id"]: node for node in task_nodes}
        defect_map = {node["id"]: node for node in defect_nodes}

        # 构建缺陷树关系
        child_defect_ids = set()
        for defect in defect_nodes:
            parent_defect_id = defect.get("parent_defect_id")
            if parent_defect_id and parent_defect_id in defect_map:
                defect_map[parent_defect_id]["children"].append(defect)
                child_defect_ids.add(defect["id"])

        root_defects = [defect for defect in defect_nodes if defect["id"] not in child_defect_ids]

        unassigned_tasks: list[dict[str, Any]] = []
        unassigned_defects: list[dict[str, Any]] = []

        # 将任务挂到对应需求
        for task in task_nodes:
            requirement_id = task.get("requirement_id")
            if requirement_id and requirement_id in requirement_map:
                requirement_map[requirement_id]["children"].append(task)
            else:
                unassigned_tasks.append(task)

        # 将缺陷挂到任务或需求，如果都没有则进入未分配
        for defect in root_defects:
            assigned = False
            task_id = defect.get("task_id")
            requirement_id = defect.get("requirement_id")
            if task_id and task_id in task_map:
                task_map[task_id]["children"].append(defect)
                assigned = True
            elif requirement_id and requirement_id in requirement_map:
                requirement_map[requirement_id]["children"].append(defect)
                assigned = True

            if not assigned:
                unassigned_defects.append(defect)

        # 计算需求层面的基础统计
        for requirement in requirement_nodes:
            task_children = [child for child in requirement["children"] if child.get("type") == "task"]
            defect_children = [child for child in requirement["children"] if child.get("type") == "defect"]
            requirement.setdefault("meta", {})
            requirement["meta"]["task_count"] = len(task_children)
            requirement["meta"]["defect_count"] = len(defect_children)

        # 计算任务层面的缺陷数量
        for task in task_nodes:
            defect_children = [child for child in task["children"] if child.get("type") == "defect"]
            task.setdefault("meta", {})
            task["meta"]["defect_count"] = len(defect_children)

        tree_nodes: list[dict[str, Any]] = list(requirement_nodes)

        # 从状态管理系统获取完成状态
        from app.utils.state_utils import get_completed_state_codes
        
        if unassigned_tasks:
            # 获取任务完成状态
            task_completed_statuses = await get_completed_state_codes(self.db, "task")
            task_completed_statuses_lower = [s.lower() for s in task_completed_statuses] if task_completed_statuses else []
            task_completed_count = len([t for t in unassigned_tasks if (t.get("status") or "").lower() in task_completed_statuses_lower])
            
            tree_nodes.append({
                "id": "group-unassigned-tasks",
                "type": "group",
                "title": "未关联需求的任务",
                "stats": {
                    "total": len(unassigned_tasks),
                    "completed": task_completed_count
                },
                "children": unassigned_tasks
            })

        if unassigned_defects:
            # 获取缺陷完成状态
            defect_completed_statuses = await get_completed_state_codes(self.db, "defect")
            defect_completed_statuses_lower = [s.lower() for s in defect_completed_statuses] if defect_completed_statuses else []
            defect_completed_count = len([d for d in unassigned_defects if (d.get("status") or "").lower() in defect_completed_statuses_lower])
            
            tree_nodes.append({
                "id": "group-unassigned-defects",
                "type": "group",
                "title": "未关联任务/需求的缺陷",
                "stats": {
                    "total": len(unassigned_defects),
                    "completed": defect_completed_count
                },
                "children": unassigned_defects
            })
        
        progress_value = completion_data.get("overall_completion", 0.0) if completion_data else 0.0
        progress_percent = max(0.0, min(100.0, round(progress_value * 100, 2)))
        
        return {
            "version": {
                "id": version_detail.get("id"),
                "name": version_detail.get("name"),
                "version_number": version_detail.get("version_number"),
                "status": version_detail.get("status"),
                "project_id": version_detail.get("project_id"),
                "project_name": version_detail.get("project_name"),
                "start_date": version_detail.get("start_date"),
                "end_date": version_detail.get("end_date"),
                "planned_release_date": version_detail.get("planned_release_date"),
                "planned_work_hours": version_detail.get("planned_work_hours"),
                "progress": progress_percent,
                "requirement_stats": requirement_stats,
                "task_stats": task_stats,
                "defect_stats": defect_stats,
                "goals": version_detail.get("goals", [])
            },
            "tree": tree_nodes,
            "items": {
                "requirements": requirement_nodes,
                "tasks": task_nodes,
                "defects": defect_nodes
            }
        }

    async def get_version_plan_view(
        self,
        version_id: str,
        current_user: dict
    ) -> Dict[str, Any]:
        """获取版本规划视图数据"""
        work_items = await self.get_version_work_items(version_id, current_user)
        version_summary = work_items.get("version", {})
        items = work_items.get("items", {})

        requirements: List[Dict[str, Any]] = items.get("requirements", []) or []
        tasks: List[Dict[str, Any]] = items.get("tasks", []) or []
        defects: List[Dict[str, Any]] = items.get("defects", []) or []

        def _status_value(entry: Dict[str, Any]) -> str:
            return str(entry.get("status") or "unknown").lower()

        summary = {
            "total_requirements": len(requirements),
            "total_tasks": len(tasks),
            "total_defects": len(defects)
        }
        summary["total_work_items"] = (
            summary["total_requirements"]
            + summary["total_tasks"]
            + summary["total_defects"]
        )
        summary["status_breakdown"] = {
            "requirements": dict(Counter(_status_value(req) for req in requirements)),
            "tasks": dict(Counter(_status_value(task) for task in tasks)),
            "defects": dict(Counter(_status_value(defect) for defect in defects))
        }

        unassigned_tasks = [task for task in tasks if not task.get("requirement_id")]
        unassigned_defects = [
            defect for defect in defects
            if not defect.get("requirement_id") and not defect.get("task_id")
        ]

        def _serialize_task(task: Dict[str, Any]) -> Dict[str, Any]:
            meta = task.get("meta") or {}
            return {
                "id": task.get("id"),
                "title": task.get("title"),
                "status": task.get("status"),
                "priority": task.get("priority"),
                "assignee_id": task.get("assignee_id"),
                "assignee_name": task.get("assignee_name"),
                "requirement_id": task.get("requirement_id"),
                "estimated_hours": task.get("estimated_hours"),
                "estimated_effort": task.get("estimated_effort"),
                "actual_hours": task.get("actual_hours"),
                "start_date": task.get("start_date"),
                "due_date": task.get("due_date"),
                "relation_type": task.get("relation_type"),
                "relation_notes": task.get("relation_notes"),
                "associated_at": task.get("associated_at"),
                "reporter_name": meta.get("reporter_name"),
                "reporter_id": meta.get("reporter_id"),
                "created_at": meta.get("created_at"),
                "updated_at": meta.get("updated_at")
            }

        def _serialize_defect(defect: Dict[str, Any]) -> Dict[str, Any]:
            meta = defect.get("meta") or {}
            return {
                "id": defect.get("id"),
                "title": defect.get("title"),
                "status": defect.get("status"),
                "priority": defect.get("priority"),
                "severity": defect.get("severity"),
                "assignee_id": defect.get("assignee_id"),
                "assignee_name": defect.get("assignee_name"),
                "requirement_id": defect.get("requirement_id"),
                "task_id": defect.get("task_id"),
                "parent_defect_id": defect.get("parent_defect_id"),
                "found_date": defect.get("found_date"),
                "resolved_date": defect.get("resolved_date"),
                "relation_type": defect.get("relation_type"),
                "relation_notes": defect.get("relation_notes"),
                "associated_at": defect.get("associated_at"),
                "reporter_name": meta.get("reporter_name"),
                "reporter_id": meta.get("reporter_id"),
                "created_at": meta.get("created_at"),
                "updated_at": meta.get("updated_at")
            }

        plan_requirements: List[Dict[str, Any]] = []
        for requirement in requirements:
            meta = requirement.get("meta") or {}
            children = requirement.get("children") or []
            requirement_tasks = [_serialize_task(child) for child in children if child.get("type") == "task"]
            requirement_defects = [_serialize_defect(child) for child in children if child.get("type") == "defect"]
            plan_requirements.append({
                "id": requirement.get("id"),
                "title": requirement.get("title"),
                "status": requirement.get("status"),
                "priority": requirement.get("priority"),
                "assignee_id": requirement.get("assignee_id"),
                "assignee_name": requirement.get("assignee_name"),
                "estimated_effort": requirement.get("estimated_effort"),
                "start_date": requirement.get("start_date"),
                "due_date": requirement.get("due_date"),
                "relation_type": requirement.get("relation_type"),
                "relation_notes": requirement.get("relation_notes"),
                "associated_at": requirement.get("associated_at"),
                "reporter_name": meta.get("reporter_name"),
                "reporter_id": meta.get("reporter_id"),
                "creator_id": meta.get("created_by"),
                "creator_name": meta.get("creator_name"),
                "created_at": meta.get("created_at"),
                "updated_at": meta.get("updated_at"),
                "task_count": len(requirement_tasks),
                "defect_count": len(requirement_defects),
                "tasks": requirement_tasks,
                "defects": requirement_defects
            })

        return {
            "version": version_summary,
            "summary": summary,
            "requirements": plan_requirements,
            "unassigned": {
                "tasks": [_serialize_task(task) for task in unassigned_tasks],
                "defects": [_serialize_defect(defect) for defect in unassigned_defects]
            }
        }
    
    async def get_version_kanban(
        self,
        version_id: str,
        current_user: dict,
        dimension: str = "requirement",
        filters: Optional[Dict[str, Any]] = None
    ) -> Dict[str, Any]:
        """获取版本的状态看板数据"""
        dimension_normalized = (dimension or "requirement").lower()
        if dimension_normalized not in {"requirement", "task", "defect"}:
            raise HTTPException(
                status_code=self._get_status_code('HTTP_400_BAD_REQUEST', 400),
                detail="Invalid dimension value"
            )

        work_items = await self.get_version_work_items(version_id, current_user)
        dimension_key = "requirements" if dimension_normalized == "requirement" else f"{dimension_normalized}s"
        dataset = work_items.get("items", {}).get(dimension_key, []) or []

        # 应用简单筛选
        status_filter = None
        assignee_filter = None
        if filters:
            status_filter = filters.get("status")
            assignee_filter = filters.get("assignee_id")

        def _match_filters(item: Dict[str, Any]) -> bool:
            if status_filter:
                status_values = status_filter if isinstance(status_filter, list) else [status_filter]
                if (item.get("status") or "").lower() not in {str(v).lower() for v in status_values}:
                    return False
            if assignee_filter:
                if str(item.get("assignee_id") or "") != str(assignee_filter):
                    return False
            return True

        filtered_dataset = [item for item in dataset if _match_filters(item)]

        status_groups: Dict[str, List[Dict[str, Any]]] = {}
        status_display: Dict[str, str] = {}
        status_order: List[str] = []

        # 加载模块对应的状态配置，保持列顺序一致
        try:
            state_cursor = self.db.states.find(
                {
                    "module_type": dimension_normalized,
                    "is_enabled": True
                },
                {
                    "code": 1,
                    "name": 1,
                    "sort_order": 1
                }
            ).sort("sort_order", ASCENDING)
            state_options = await state_cursor.to_list(length=None)
        except Exception:
            state_options = []

        for state in state_options:
            code = state.get("code")
            if not code:
                continue
            status_order.append(code)
            status_display[code] = state.get("name") or code

        def _to_kanban_item(item: Dict[str, Any]) -> Dict[str, Any]:
            base = {
                "id": item.get("id"),
                "title": item.get("title"),
                "status": item.get("status"),
                "priority": item.get("priority"),
                "assignee_id": item.get("assignee_id"),
                "assignee_name": item.get("assignee_name"),
                "due_date": item.get("due_date"),
                "start_date": item.get("start_date"),
                "meta": item.get("meta") or {},
                "type": dimension_normalized
            }
            if dimension_normalized == "task":
                base["requirement_id"] = item.get("requirement_id")
                base["estimated_hours"] = item.get("estimated_hours")
                base["actual_hours"] = item.get("actual_hours")
            if dimension_normalized == "defect":
                base["severity"] = item.get("severity")
                base["requirement_id"] = item.get("requirement_id")
                base["task_id"] = item.get("task_id")
            if dimension_normalized == "requirement":
                base["progress"] = (item.get("meta") or {}).get("progress")
            return base

        for entry in filtered_dataset:
            status_value = entry.get("status") or "unknown"
            if status_value not in status_groups:
                status_groups[status_value] = []
                if status_value not in status_display:
                    status_display[status_value] = status_value
                if status_value not in status_order:
                    status_order.append(status_value)
            status_groups[status_value].append(_to_kanban_item(entry))

        # 确保状态列表完整（包含没有数据的状态）
        for status_value in status_order:
            status_groups.setdefault(status_value, [])
        # 将额外状态追加到顺序列表中
        for status_value in status_groups.keys():
            if status_value not in status_order:
                status_order.append(status_value)

        columns = []
        for status_value in status_order:
            columns.append({
                "status": status_value,
                "title": status_display.get(status_value, status_value),
                "items": status_groups.get(status_value, [])
            })

        summary_stats = {
            "total": len(filtered_dataset),
            "by_status": {status: len(status_groups.get(status, [])) for status in status_order}
        }

        return {
            "version": work_items.get("version"),
            "dimension": dimension_normalized,
            "columns": columns,
            "meta": {
                "filters": filters or {},
                "stats": summary_stats
            }
        }
    
    async def create_version(
        self,
        version_data: VersionCreate,
        current_user: dict
    ) -> Dict[str, Any]:
        """创建版本"""
        # 权限检查
        user_permissions = current_user.get("permissions", [])
        if "*" not in user_permissions and Permissions.VERSION_CREATE not in user_permissions:
            raise HTTPException(
                status_code=self._get_status_code('HTTP_403_FORBIDDEN', 403),
                detail="Permission denied"
            )
        
        # 验证项目ID并检查访问权限
        project_obj_id = self._validate_object_id(version_data.project_id, "project_id")
        project = await self._check_project_access(project_obj_id, current_user, include_public=True)
        
        # 检查版本号是否在项目中唯一
        existing_version = await self.db.versions.find_one({
            "project_id": project_obj_id,
            "version_number": version_data.version_number
        })
        if existing_version:
            raise HTTPException(
                status_code=self._get_status_code('HTTP_400_BAD_REQUEST', 400),
                detail="Version number already exists in this project"
            )
        
        # 创建版本文档
        now = datetime.utcnow()
        version_dict = version_data.dict(exclude_unset=False)
        
        # 处理枚举类型的转换
        release_type_value = version_dict.get("release_type")
        if release_type_value and hasattr(release_type_value, 'value'):
            release_type_value = release_type_value.value
        
        environment_value = version_dict.get("environment")
        if environment_value and hasattr(environment_value, 'value'):
            environment_value = environment_value.value
        
        # 从状态管理系统获取默认状态
        from app.utils.state_utils import get_default_state_code
        default_status = await get_default_state_code(self.db, "version")
        
        version_doc = {
            "name": version_data.name,
            "version_number": version_data.version_number,
            "description": version_data.description,
            "project_id": project_obj_id,
            "start_date": version_data.start_date,
            "end_date": version_data.end_date,
            "status": version_dict.get("status", default_status or ""),
            "release_type": release_type_value,
            "environment": environment_value,
            "planned_release_date": version_data.planned_release_date,
            "planned_work_hours": float(version_dict.get("planned_work_hours", 0.0) or 0.0),
            "actual_release_date": None,
            "release_notes": version_dict.get("release_notes"),
            "release_config": version_data.release_config.model_dump() if version_data.release_config else {},
            "metadata": version_dict.get("metadata") or {},
            "goals": [
                goal.model_dump()
                if hasattr(goal, "model_dump")
                else goal.dict()
                if hasattr(goal, "dict")
                else goal
                for goal in version_data.goals or []
            ],
            "created_by": ObjectId(current_user.get("user_id")),
            "created_at": now,
            "updated_at": now
        }
        
        # 插入版本
        result = await self.db.versions.insert_one(version_doc)
        
        # 返回创建的版本
        created_version = await self.db.versions.find_one({"_id": result.inserted_id})
        
        return {
            "id": str(created_version["_id"]),
            "name": created_version["name"],
            "version_number": created_version["version_number"],
            "description": created_version.get("description"),
            "project_id": str(created_version["project_id"]),
            "start_date": created_version.get("start_date"),
            "end_date": created_version.get("end_date"),
            "status": created_version["status"],
            "release_type": created_version["release_type"],
            "environment": created_version["environment"],
            "planned_release_date": created_version.get("planned_release_date"),
            "actual_release_date": created_version.get("actual_release_date"),
            "planned_work_hours": float(created_version.get("planned_work_hours") or 0.0),
            "release_notes": created_version.get("release_notes"),
            "release_config": created_version.get("release_config", {}),
            "metadata": created_version.get("metadata", {}),
            "goals": created_version.get("goals", []),
            "created_by": str(created_version["created_by"]),
            "created_at": created_version["created_at"],
            "updated_at": created_version["updated_at"]
        }
    
    async def update_version(
        self,
        version_id: str,
        version_data: VersionUpdate,
        current_user: dict
    ) -> Dict[str, Any]:
        """更新版本"""
        version_obj_id = self._validate_object_id(version_id, "version_id")
        
        # 查询版本
        version = await self.db.versions.find_one({"_id": version_obj_id})
        if not version:
            raise HTTPException(
                status_code=self._get_status_code('HTTP_404_NOT_FOUND', 404),
                detail="Version not found"
            )
        
        # 权限检查
        user_permissions = current_user.get("permissions", [])
        user_id = ObjectId(current_user.get("user_id"))
        
        # 管理员或拥有更新权限的用户可以更新
        if "*" not in user_permissions and Permissions.VERSION_UPDATE not in user_permissions:
            # 检查是否是版本创建者或项目成员
            if version["created_by"] != user_id:
                await self._check_project_access(version["project_id"], current_user, include_public=False)
        
        # 构建更新数据
        update_data = {"updated_at": datetime.utcnow()}
        update_fields = version_data.dict(exclude_unset=True)
        
        # 如果更新版本号，检查是否在项目中唯一
        if "version_number" in update_fields and update_fields["version_number"] and update_fields["version_number"] != version["version_number"]:
            existing_version = await self.db.versions.find_one({
                "project_id": version["project_id"],
                "version_number": update_fields["version_number"],
                "_id": {"$ne": version_obj_id}
            })
            if existing_version:
                raise HTTPException(
                    status_code=self._get_status_code('HTTP_400_BAD_REQUEST', 400),
                    detail="Version number already exists in this project"
                )
        
        for field, value in update_fields.items():
            if field in ["release_config", "metadata"] and value:
                update_data[field] = value.model_dump() if hasattr(value, 'model_dump') else value
            elif field == "goals":
                if value is None:
                    update_data[field] = []
                else:
                    update_data[field] = [
                        goal.model_dump()
                        if hasattr(goal, "model_dump")
                        else goal.dict()
                        if hasattr(goal, "dict")
                        else goal
                        for goal in value
                    ]
            elif field == "planned_work_hours":
                if value is not None:
                    update_data[field] = float(value)
                else:
                    update_data[field] = 0.0
            else:
                update_data[field] = value
        
        # 更新版本
        await self.db.versions.update_one(
            {"_id": version_obj_id},
            {"$set": update_data}
        )
        
        # 返回更新后的版本
        updated_version = await self.db.versions.find_one({"_id": version_obj_id})
        
        return {
            "id": str(updated_version["_id"]),
            "name": updated_version["name"],
            "version_number": updated_version["version_number"],
            "description": updated_version.get("description"),
            "project_id": str(updated_version["project_id"]),
            "start_date": updated_version.get("start_date"),
            "end_date": updated_version.get("end_date"),
            "status": updated_version["status"],
            "release_type": updated_version["release_type"],
            "environment": updated_version["environment"],
            "planned_release_date": updated_version.get("planned_release_date"),
            "actual_release_date": updated_version.get("actual_release_date"),
            "planned_work_hours": float(updated_version.get("planned_work_hours") or 0.0),
            "release_notes": updated_version.get("release_notes"),
            "release_config": updated_version.get("release_config", {}),
            "metadata": updated_version.get("metadata", {}),
            "goals": updated_version.get("goals", []),
            "created_by": str(updated_version["created_by"]),
            "created_at": updated_version["created_at"],
            "updated_at": updated_version["updated_at"]
        }
    
    async def delete_version(
        self,
        version_id: str,
        current_user: dict
    ) -> None:
        """删除版本"""
        version_obj_id = self._validate_object_id(version_id, "version_id")
        
        # 查询版本
        version = await self.db.versions.find_one({"_id": version_obj_id})
        if not version:
            raise HTTPException(
                status_code=self._get_status_code('HTTP_404_NOT_FOUND', 404),
                detail="Version not found"
            )
        
        # 权限检查
        user_permissions = current_user.get("permissions", [])
        user_id = ObjectId(current_user.get("user_id"))
        
        # 管理员或拥有删除权限的用户可以删除
        if "*" not in user_permissions and Permissions.VERSION_DELETE not in user_permissions:
            # 检查是否是版本创建者
            if version["created_by"] != user_id:
                # 检查是否是项目管理员或经理
                project = await self.db.projects.find_one({
                    "_id": version["project_id"],
                    "$or": [
                        {"created_by": user_id},
                        {
                            "members": {
                                "$elemMatch": {
                                    "user_id": user_id,
                                    "role": {"$in": ["admin", "manager"]}
                                }
                            }
                        }
                    ]
                })
                if not project:
                    raise HTTPException(
                        status_code=self._get_status_code('HTTP_403_FORBIDDEN', 403),
                        detail="Permission denied"
                    )
        
        # 检查版本是否已发布
        released_status_code = await get_state_code_by_name_keywords(self.db, "version", ["已发布", "发布", "released"])
        if released_status_code and version["status"] == released_status_code:
            raise HTTPException(
                status_code=self._get_status_code('HTTP_400_BAD_REQUEST', 400),
                detail="Cannot delete released version"
            )
        
        # 检查关联关系
        task_relations_count = await self.db.version_task_relations.count_documents({"version_id": version_obj_id})
        defect_relations_count = await self.db.version_defect_relations.count_documents({"version_id": version_obj_id})
        requirement_relations_count = await self.db.version_requirement_relations.count_documents({"version_id": version_obj_id})
        # 测试用例和测试套件统计已移除（测试用例不再关联版本）
        # 如果需要测试相关统计，请通过测试报告模块获取
        release_records_count = await self.db.release_records.count_documents({"version_id": version_obj_id})
        
        if (task_relations_count > 0 or defect_relations_count > 0 or requirement_relations_count > 0 or 
            release_records_count > 0):
            relation_details = []
            if task_relations_count > 0:
                relation_details.append(f"任务关联({task_relations_count}个)")
            if defect_relations_count > 0:
                relation_details.append(f"缺陷关联({defect_relations_count}个)")
            if requirement_relations_count > 0:
                relation_details.append(f"需求关联({requirement_relations_count}个)")
            # 测试用例和测试套件检查已移除
            if release_records_count > 0:
                relation_details.append(f"发布记录({release_records_count}个)")
            
            # 创建一个包含详细信息的异常
            error_detail = HTTPException(
                status_code=self._get_status_code('HTTP_400_BAD_REQUEST', 400),
                detail=f"无法删除版本：该版本下存在关联数据，请先解除关联关系后再删除。关联项：{', '.join(relation_details)}"
            )
            # 添加详细信息到异常对象（用于端点层获取）
            error_detail.relation_details = {
                "task_relations": task_relations_count,
                "defect_relations": defect_relations_count,
                "requirement_relations": requirement_relations_count,
                # 测试用例和测试套件统计已移除
                "release_records": release_records_count
            }
            raise error_detail
        
        # 删除相关数据
        await self.db.version_task_relations.delete_many({"version_id": version_obj_id})
        await self.db.version_defect_relations.delete_many({"version_id": version_obj_id})
        await self.db.version_requirement_relations.delete_many({"version_id": version_obj_id})
        await self.db.release_records.delete_many({"version_id": version_obj_id})
        
        # 删除版本
        await self.db.versions.delete_one({"_id": version_obj_id})
    
    async def get_completion_rate(self, version_id: ObjectId, use_cache: bool = True) -> Dict:
        """
        获取版本完成度（对外统一接口）
        
        Args:
            version_id: 版本ID
            use_cache: 是否使用缓存
        
        Returns:
            完成度数据
        """
        if use_cache:
            from app.core.version_completion_cache import version_completion_cache
            return await version_completion_cache.get_completion(
                version_id, self.db, force_refresh=not use_cache
            )
        else:
            return await self.completion_service.calculate_completion_rate(version_id)
    
    async def check_and_transition(
        self,
        version_id: ObjectId,
        trigger_source: Optional[str] = None
    ) -> Optional[Dict]:
        """
        检查并执行状态流转（对外统一接口）
        
        Args:
            version_id: 版本ID
            trigger_source: 触发来源
        
        Returns:
            流转结果信息，如果未流转则返回None
        """
        return await self.auto_transition_service.check_and_transition(
            version_id, trigger_source
        )
    
    async def manual_transition(
        self,
        version_doc: Dict[str, Any],
        to_status: str,
        current_user: dict,
        reason: str = "",
        comment: str = ""
    ) -> Dict[str, Any]:
        """手工触发版本状态流转"""
        version_id = version_doc.get("_id")
        if not isinstance(version_id, ObjectId):
            version_id = self._validate_object_id(version_doc.get("id"), "version_id")
        
        # 从状态管理系统获取默认状态
        from app.utils.state_utils import get_default_state_code
        default_status = await get_default_state_code(self.db, "version")
        current_status = version_doc.get("status", default_status or "")
        if current_status == to_status:
            raise HTTPException(
                status_code=self._get_status_code('HTTP_400_BAD_REQUEST', 400),
                detail="目标状态与当前状态相同，无需流转"
            )
        
        rule = VersionTransitionRules.get_rule(current_status, to_status)
        if not rule:
            raise HTTPException(
                status_code=self._get_status_code('HTTP_400_BAD_REQUEST', 400),
                detail=f"不允许从 {current_status} 流转到 {to_status}"
            )
        
        if rule.require_reason and not reason:
            raise HTTPException(
                status_code=self._get_status_code('HTTP_400_BAD_REQUEST', 400),
                detail="该状态流转需要填写原因"
            )
        
        user_permissions = current_user.get("permissions", [])
        if rule.require_approval and (
            "*" not in user_permissions
            and Permissions.VERSION_TRANSITION_APPROVE not in user_permissions
        ):
            raise HTTPException(
                status_code=self._get_status_code('HTTP_403_FORBIDDEN', 403),
                detail="该状态流转需要审批，请先提交审批申请"
            )
        
        completion_data: Dict[str, Any] = {}
        readiness_data: Dict[str, Any] = {}
        
        # 从状态管理系统获取草稿和规划状态
        from app.utils.state_utils import get_state_code_by_name_keywords
        draft_code = await get_state_code_by_name_keywords(self.db, "version", ["草稿", "draft"])
        planning_code = await get_state_code_by_name_keywords(self.db, "version", ["规划中", "planning"])
        early_statuses = [s for s in [draft_code, planning_code] if s]
        if current_status in early_statuses:
            readiness_data = await self.completion_service.calculate_readiness_metrics(version_id, version_doc)
        else:
            completion_data = await self.completion_service.calculate_completion_rate(version_id)
        
        is_met, unmet_conditions = await VersionTransitionRules.check_rule_conditions(
            rule, completion_data, readiness_data, version_doc, self.db
        )
        
        if not is_met:
            raise HTTPException(
                status_code=self._get_status_code('HTTP_400_BAD_REQUEST', 400),
                detail={
                    "message": "状态流转条件未满足",
                    "unmet_conditions": unmet_conditions
                }
            )
        
        operator_id = self._to_object_id(current_user.get("user_id"), "user_id")
        
        transition_data = completion_data or readiness_data or {}
        update_data: Dict[str, Any] = {
            "status": to_status,
            "updated_at": datetime.utcnow()
        }
        # 动态判断是否为已发布状态（包含"已发布"、"发布"等关键词）
        from app.utils.state_utils import get_state_code_by_name_keywords
        released_status_code = await get_state_code_by_name_keywords(self.db, "version", ["已发布", "发布", "released"])
        if released_status_code and to_status == released_status_code and not version_doc.get("actual_release_date"):
            update_data["actual_release_date"] = datetime.utcnow()
        
        update_result = await self.db.versions.update_one(
            {"_id": version_id},
            {"$set": update_data}
        )
        if update_result.modified_count == 0:
            raise HTTPException(
                status_code=self._get_status_code('HTTP_500_INTERNAL_SERVER_ERROR', 500),
                detail="状态流转失败，请稍后重试"
            )
        
        history_doc = {
            "version_id": version_id,
            "from_status": current_status,
            "to_status": to_status,
            "transition_type": "manual",
            "trigger_source": "manual",
            "operator_id": operator_id,
            "reason": reason or None,
            "comment": comment or None,
            "completion_data": transition_data,
            "created_at": datetime.utcnow()
        }
        try:
            await self.db.version_status_history.insert_one(history_doc)
        except Exception as history_error:
            logger.warning(
                "记录版本状态流转历史失败: %s", str(history_error), exc_info=True
            )
        
        try:
            from app.core.version_completion_cache import version_completion_cache
            await version_completion_cache.invalidate(version_id)
        except Exception as cache_error:
            logger.debug(
                "清理版本完成度缓存失败: %s", str(cache_error), exc_info=True
            )
        
        return {
            "version_id": str(version_id),
            "from_status": current_status,
            "to_status": to_status,
            "transition_type": "manual",
            "trigger_source": "manual",
            "operator_id": str(operator_id) if operator_id else None,
            "reason": reason or None,
            "comment": comment or None,
            "success": True
        }
    
    async def get_transition_readiness(
        self,
        version_id: ObjectId,
        target_status: Optional[str] = None
    ) -> Dict:
        """
        获取版本状态流转准备情况（对外统一接口）
        
        Args:
            version_id: 版本ID
            target_status: 目标状态（可选）
        
        Returns:
            流转准备情况
        """
        return await self.auto_transition_service.get_transition_readiness(
            version_id, target_status
        )

